/*
 * Copyright 2011 Stefan Ferstl
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.debmavenplugin.archiver;

import java.io.File;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.codehaus.plexus.util.PathTool;

/**
 * An abstract {@link IOFileFilter} implementation that collects the traversed
 * files and/or directories. The traversed files are collected in a {@link Set}
 * of type <code>T</code>. Subclasses need to override the two template methods
 * {@link #collectFile(File)} and {@link #getCollectedFiles()}.
 */
public abstract class AbstractCollectingFileFilter<T> implements IOFileFilter {

  private final ArchiveMetaData metaData;
  private final boolean useRelativePaths;

  /**
   * Constructor.
   *
   * @param metaData Metadata describing the files to be collected.
   * @param useRelativePaths <code>true</code> if the created file path relative
   * to the {@link ArchiveMetaData}'s base directory should be relative,
   * <code>false</code> if it should be absolute.
   * <pre>
   *   Path Examples
   *   -------------
   *   Relative path: ./usr/bin/myfile.bin
   *   Absolute path: usr/bin/myfile.bin
   * </pre>
   */
  public AbstractCollectingFileFilter(ArchiveMetaData metaData, boolean useRelativePaths) {
    this.metaData = metaData;
    this.useRelativePaths = useRelativePaths;
  }

  /**
   * Accepts the file if it is not excluded in the metadata and calls the
   * {@link #accept(File)} hook.
   * @param file File.
   */
  @Override
  public final boolean accept(File file) {
    boolean accept = false;
    if (!this.metaData.isExcluded(file)) {
      collectFile(file);
      accept = true;
    }
    return accept;
  }

  /**
   * Similar to <code>accept(new File(dir, name))</code>.
   * @param dir Directory.
   * @param name File name.
   */
  @Override
  public final boolean accept(File dir, String name) {
    return accept(new File(dir, name));
  }

  /**
   * Returns the metadata of the files to be collected.
   * @return Metadata of the files to be collected.
   */
  protected ArchiveMetaData getMetaData() {
    return this.metaData;
  }

  /**
   * Creates a path to the given file relative to the base directory. For
   * example, the created path for a file located at
   * <code>/tmp/test-folder/foo/myfile.txt/code>
   * and a given base directory of <code>/tmp/test-folder</code> will return
   * <code>foo/myfile.txt</code> or <code>./foo/myfile.txt</code> depending on
   * the value of {@link #useRelativePaths}.
   *
   * @param baseDir Base directory.
   * @param file File.
   * @return The created path relative to the base directory.
   */
  protected String createPath(File baseDir, File file) {
    String basedirPath = FilenameUtils.normalize(baseDir.getAbsolutePath());
    String filePath = FilenameUtils.normalize(file.getAbsolutePath());

    String archivePath = PathTool.getRelativeFilePath(basedirPath, filePath);
    String relativePrefix = this.useRelativePaths ? "./" : "";
    String archivePathNormalized = relativePrefix + FilenameUtils.separatorsToUnix(FilenameUtils.normalize(archivePath));

    return archivePathNormalized;
  }

  /**
   * Hook method called by {@link #accept(File)} for accepted file. This method
   * is supposed to do the actual work of collecting the accepted file, i.e.
   * wrapping it in an object of type <code>&lt;T&gt;</code> and storing it in
   * the {@link Set} that will be returned in {@link #getCollectedFiles()}.
   *
   * @param file File to be collected.
   */
  protected abstract void collectFile(File file);

  /**
   * Returns the set of collected files.
   * @return The set of collected files.
   */
  public abstract Set<T> getCollectedFiles();

}